﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting;
using Tsanie.ShellExt.Definitions;

namespace Tsanie.ShellExt
{
    class ProviderStructure
    {
        public string Class { get; set; }
        public BaseProvider Provider { get; set; }
    }
    class Manager
    {
        /// <summary>
        /// 是否已装载插件
        /// </summary>
        public static bool IsPluginLoaded
        {
            get { return _pluginDomain != null; }
        }

        #region - Privates -

        // 插件程序池
        static AppDomain _pluginDomain = null;
        // 插件字典
        static Dictionary<string, ProviderStructure> _pluginThumbnails = new Dictionary<string, ProviderStructure>();
        static Dictionary<string, ProviderStructure> _pluginContextMenus = new Dictionary<string, ProviderStructure>();
        static Dictionary<string, ProviderStructure> _pluginInfoTips = new Dictionary<string, ProviderStructure>();
        static Dictionary<string, ProviderStructure> _pluginPropertySheets = new Dictionary<string, ProviderStructure>();
        static Assembly _managerAssembly = null;

        #endregion

        static Manager()
        {
            // 初始化加载插件
            LoadPlugins();
        }

        /// <summary>
        /// 装载插件
        /// </summary>
        public static bool LoadPlugins()
        {
            try
            {
                // 若插件未卸载
                if (_pluginDomain != null)
                {
                    Common.OutputDebugString("Please unload the plugins first. @" + AppDomain.CurrentDomain.FriendlyName);
                    return false;
                }

                // 工作路径
                string path = Assembly.GetExecutingAssembly().Location;
                path = path.Substring(0, path.LastIndexOf('\\') + 1);

                // .NET的一个bug，remote调用后在Unwrap时会提示缺少程序集，需要手动指定加载 ★
                AppDomain.CurrentDomain.AssemblyResolve -= currentDomain_AssemblyResolve;
                AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve;

                AppDomainSetup ads = new AppDomainSetup();
                ads.ApplicationName = "Tsanie.Plugins";
                ads.ApplicationBase = path;

                _pluginDomain = AppDomain.CreateDomain("Tsanie.PluginsDomain", null, ads);

                // 插件配置文件
                bool needRefresh = false;
                using (StreamReader reader = new StreamReader(path + Common.CONFIG_NAME))
                {
                    string line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (line.Length > 0 && line[0] != '#')
                        {
                            string[] kv = line.Split(',');
                            if (kv.Length != 2)
                            {
                                continue;
                            }
                            needRefresh |= LoadPlugin(kv[0], kv[1]);
                        }
                    }
                    reader.Close();
                }
                if (needRefresh)
                {
                    // 刷新 Expolorer
                    Common.SHChangeNotify(Common.SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero);
                }
                return true;
            }
            catch (Exception e)
            {
                Common.OutputError("LoadPlugins(), @" + AppDomain.CurrentDomain.FriendlyName, e);
            }
            return false;
        }

        /// <summary>
        /// 卸载插件
        /// </summary>
        public static bool UnloadPlugins()
        {
            if (_pluginDomain != null)
            {
                try
                {
                    bool needRefresh = false;
                    needRefresh |= UnloadDictionary("{BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}", _pluginThumbnails);
                    needRefresh |= UnloadDictionary("ContextMenuHandlers\\" + Common.CONFIG_MENUTYPE, _pluginContextMenus);
                    needRefresh |= UnloadDictionary("{00021500-0000-0000-C000-000000000046}", _pluginInfoTips);
                    needRefresh |= UnloadDictionary("PropertySheetHandlers\\" + Common.CONFIG_PROPERTYTYPE, _pluginPropertySheets);
                    // TODO: IconHandler
                    AppDomain.Unload(_pluginDomain);
                    _pluginDomain = null;

                    return needRefresh;
                }
                catch (Exception e)
                {
                    Common.OutputError("UnloadPlugins(), @" + AppDomain.CurrentDomain.FriendlyName, e);
                }
            }
            return false;
        }

        #region - Get Provider -

        public static ThumbnailBaseProvider GetThumbnailProvider(string ext)
        {
            return GetCacheProvider(ext, _pluginThumbnails) as ThumbnailBaseProvider;
        }
        public static ContextMenuBaseProvider GetContextMenuProvider(string ext)
        {
            return GetCacheProvider(ext, _pluginContextMenus) as ContextMenuBaseProvider;
        }
        public static InfoTipBaseProvider GetInfoTipProvider(string ext)
        {
            return GetCacheProvider(ext, _pluginInfoTips) as InfoTipBaseProvider;
        }
        public static PropertySheetBaseProvider GetPropertySheetProvider(string ext)
        {
            return GetCacheProvider(ext, _pluginPropertySheets) as PropertySheetBaseProvider;
        }

        public static T GetCacheProvider<T>(FileCollection files) where T : BaseProvider
        {
            foreach (string file in files)
            {
                string ext = "." + file.Substring(file.LastIndexOf('.') + 1);
                BaseProvider provider;
                switch (typeof(T).Name)
                {
                    case "ThumbnailBaseProvider":
                        if ((provider = GetThumbnailProvider(ext)) != null)
                        {
                            return (T)provider;
                        }
                        break;
                    case "ContextMenuBaseProvider":
                        if ((provider = GetContextMenuProvider(ext)) != null)
                        {
                            return (T)provider;
                        }
                        break;
                    case "InfoTipBaseProvider":
                        if ((provider = GetInfoTipProvider(ext)) != null)
                        {
                            return (T)provider;
                        }
                        break;
                    case "PropertySheetBaseProvider":
                        if ((provider = GetPropertySheetProvider(ext)) != null)
                        {
                            return (T)provider;
                        }
                        break;
                    case "IconBaseProvider":
                        // TODO: Icon
                        break;
                }
            }
            return null;
        }
        // 获取扩展类型对应 Provider
        static BaseProvider GetCacheProvider(string ext, Dictionary<string, ProviderStructure> dic)
        {
            ProviderStructure ps;
            if (dic.TryGetValue(ext, out ps))
            {
                try
                {
                    string s = ps.Provider.ProviderName;
                    return ps.Provider;
                }
                catch (RemotingException)
                {
                    try
                    {
                        string[] kv = ps.Class.Split(',');
#if DEBUG
                        Common.OutputDebugString("Proxy '{0}' is disconnected, recreating...", ps.Class);
#endif
                        BaseProvider pv = (BaseProvider)_pluginDomain.CreateInstanceAndUnwrap(kv[0], kv[1]);
                        ps.Provider = pv;
                        return pv;
                    }
                    catch (Exception ex)
                    {
                        Common.OutputError(string.Format("GetCacheProvider('{0}'), {1}", ext, ps.Class), ex);
                    }
                }
                catch (Exception e)
                {
                    Common.OutputError(string.Format("GetCacheProvider('{0}'), {1}", ext, ps.Class), e);
                }
            }
            else
            {
                Common.OutputDebugString("GetCacheProvider('{0}'):: There's no plugin provider.", ext);
            }
            return null;
        }

        #endregion

        // 卸载单类字典
        static bool UnloadDictionary(string key, Dictionary<string, ProviderStructure> dic)
        {
            bool changed = false;
            lock (dic)
            {
                foreach (KeyValuePair<string, ProviderStructure> kv in dic)
                {
                    changed |= Common.UnregisterKey(kv.Key, key, kv.Value.Provider.Type);
                }
                dic.Clear();
            }
            return changed;
        }
        // 加载单个插件到对应字典
        static bool LoadPluginDictionary(string pName, BaseProvider provider, Dictionary<string, ProviderStructure> dic, string regKey)
        {
            string[] exts = provider.Exts;
            if (exts == null || exts.Length == 0)
            {
                Common.OutputDebugString("Exts of '{0}' can't be empty.", provider);
                return false;
            }
            bool needRefresh = false;
            foreach (string ext in exts)
            {
                // 是否已加载过此插件
                if (dic.ContainsKey(ext))
                {
                    Common.OutputDebugString("The extension '{0}' is already exist.", ext);
                    continue;
                }
                dic.Add(ext, new ProviderStructure
                {
                    Class = pName,
                    Provider = provider
                });

                needRefresh |= Common.RegisterKey(ext, regKey, provider.Type);
#if DEBUG
                Common.OutputDebugString("Provider '{0}, {1}' added.", ext, pName);
#endif
            }
            return needRefresh;
        }
        // 加载单个插件
        static bool LoadPlugin(string assembly, string className)
        {
            try
            {
                // 远程应用池代理
                BaseProvider provider = (BaseProvider)_pluginDomain.CreateInstanceAndUnwrap(assembly, className);
                string pName = assembly + "," + className;

                switch (provider.Type)
                {
                    case ProviderType.Thumbnail:
                        return LoadPluginDictionary(pName, provider, _pluginThumbnails, "{BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}");
                    case ProviderType.ContextMenu:
                        return LoadPluginDictionary(pName, provider, _pluginContextMenus, "ContextMenuHandlers\\" + Common.CONFIG_MENUTYPE);
                    case ProviderType.InfoTip:
                        return LoadPluginDictionary(pName, provider, _pluginInfoTips, "{00021500-0000-0000-C000-000000000046}");
                    case ProviderType.PropertySheet:
                        return LoadPluginDictionary(pName, provider, _pluginPropertySheets, "PropertySheetHandlers\\" + Common.CONFIG_PROPERTYTYPE);
                    // TODO: IconHandler
                }
            }
            catch (Exception e)
            {
                Common.OutputError(string.Format("LoadPlugin('{0}', '{1}')", assembly, className), e);
            }
            return false;
        }
        // 手动加载程序集的方法
        static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            if (_managerAssembly == null)
            {
                _managerAssembly = Assembly.LoadFrom(Assembly.GetExecutingAssembly().Location);
            }
            return _managerAssembly;
        }

    }
}
